{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码， 原文地址 https://blog.csdn.net/qq_32188669/article/details/93491832\n",
    "\n",
    "python Gstreamer 编写 mp4 视频播放器\n",
    "-----------------------------\n",
    "\n",
    "### 对使用 Gstreamer 编写播放器的理解\n",
    "\n",
    "实现图解：  \n",
    "![](https://img-blog.csdnimg.cn/20190709195447794.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyMTg4NjY5,size_16,color_FFFFFF,t_70)  \n",
    "使用 Gstreamer 编写播放器有几个重点：\n",
    "\n",
    "#### 1. 元件的挑选：\n",
    "\n",
    "根据视频的封装格式和封装内的视频、音频编码格式挑选所需要的解封装和解码元件。视频、音频编码的格式可以从视频的属性中获得：![](https://img-blog.csdnimg.cn/201906241626380.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyMTg4NjY5,size_16,color_FFFFFF,t_70)  \n",
    "如图中视频的编码格式为 H.264, 音频的编码格式为 AAC。而相关元件可以使用 gst-inspect 命令来搜索。  \n",
    "![](https://img-blog.csdnimg.cn/20190624163354624.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyMTg4NjY5,size_16,color_FFFFFF,t_70)\n",
    "\n",
    "#### 2. 元件的连接\n",
    "\n",
    "元件连接部分重点在设置回调函数在 demuxer 产生新衬垫时与队列衬垫连接。使用 gst-inspect 命令可以获得元件衬垫的 template，以此区分视频和音频流来完成产生衬垫时的连接，若衬垫属性为 sometimes（即随机衬垫）则需要使用回调函数来动态连接衬垫。  \n",
    "命令：gst-inspect-1.0 qtdemux  \n",
    "![](https://img-blog.csdnimg.cn/2019062416404253.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyMTg4NjY5,size_16,color_FFFFFF,t_70)\n",
    "\n",
    "### mp4 简易播放器：\n",
    "\n",
    "```\n",
    "import gi\n",
    "gi.require_version('Gst', '1.0')\n",
    "from gi.repository import Gst, GObject, GLib\n",
    "\n",
    "Gst.init(None)\n",
    "\n",
    "#设置回调函数在demuxer产生新衬垫时与队列衬垫连接\n",
    "def cb_demuxer_newpad(src, pad, dst,dst2):\n",
    "    if pad.get_property(\"template\").name_template == \"video_%u\":\n",
    "        vdec_pad = dst.get_static_pad(\"sink\")\n",
    "        pad.link(vdec_pad)\n",
    "    elif pad.get_property(\"template\").name_template == \"audio_%u\":\n",
    "        adec_pad = dst2.get_static_pad(\"sink\")\n",
    "        pad.link(adec_pad)\n",
    "        \n",
    "#创建elements\n",
    "pipe = Gst.Pipeline.new(\"test\")\n",
    "src = Gst.ElementFactory.make(\"filesrc\", \"src\")\n",
    "demuxer = Gst.ElementFactory.make(\"qtdemux\", \"demux\")\n",
    "\n",
    "#创建视频队列元件\n",
    "decodebin = Gst.ElementFactory.make(\"avdec_h264\", \"decode\")\n",
    "queuev = Gst.ElementFactory.make(\"queue\", \"queue\")\n",
    "conv = Gst.ElementFactory.make(\"videoconvert\", \"conv\")\n",
    "sink = Gst.ElementFactory.make(\"xvimagesink\", \"sink\")\n",
    "\n",
    "#创建音频队列元件\n",
    "decodebina = Gst.ElementFactory.make(\"faad\", \"decodea\")\n",
    "queuea = Gst.ElementFactory.make(\"queue\", \"queuea\")\n",
    "conva = Gst.ElementFactory.make(\"audioconvert\", \"conva\")\n",
    "sinka = Gst.ElementFactory.make(\"autoaudiosink\", \"sinka\")\n",
    "\n",
    "#获取播放地址（这里location表示视频和代码在同一个项目下）\n",
    "src.set_property(\"location\", \"a.mp4\")\n",
    "demuxer.connect(\"pad-added\", cb_demuxer_newpad, queuev,queuea)\n",
    "\n",
    "#向管道中添加元件\n",
    "pipe.add(src)\n",
    "pipe.add(demuxer)\n",
    "pipe.add(queuev)\n",
    "pipe.add(decodebin)\n",
    "pipe.add(conv)\n",
    "pipe.add(sink)\n",
    "\n",
    "pipe.add(queuea)\n",
    "pipe.add(decodebina)\n",
    "pipe.add(conva)\n",
    "pipe.add(sinka)\n",
    "\n",
    "#连接元件\n",
    "src.link(demuxer)\n",
    "queuev.link(decodebin)\n",
    "decodebin.link(conv)\n",
    "conv.link(sink)\n",
    "\n",
    "queuea.link(decodebina)\n",
    "decodebina.link(conva)\n",
    "conva.link(sinka)\n",
    "\n",
    "#修改状态为播放\n",
    "pipe.set_state(Gst.State.PLAYING)\n",
    "\n",
    "mainloop = GLib.MainLoop()\n",
    "mainloop.run()\n",
    "```\n",
    "\n",
    "### 带有 gtk 界面的 mp4 播放器\n",
    "\n",
    "```\n",
    "import os\n",
    "import gi\n",
    "gi.require_version('Gst', '1.0')\n",
    "gi.require_version('Gtk', '3.0')\n",
    "from gi.repository import Gst, GObject, Gtk\n",
    "class GTK_Main(object):\n",
    "    def __init__(self):\n",
    "        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)\n",
    "        window.set_title(\"Mpeg2-Player\")\n",
    "        window.set_default_size(500, 400)\n",
    "        window.connect(\"destroy\", Gtk.main_quit, \"WM destroy\")\n",
    "        vbox = Gtk.VBox()\n",
    "        window.add(vbox)\n",
    "        hbox = Gtk.HBox()\n",
    "        vbox.pack_start(hbox, False, False, 0)\n",
    "        self.entry = Gtk.Entry()\n",
    "        hbox.add(self.entry)\n",
    "        self.button = Gtk.Button(\"Start\")\n",
    "        hbox.pack_start(self.button, False, False, 0)\n",
    "        self.button.connect(\"clicked\", self.start_stop)\n",
    "        self.movie_window = Gtk.DrawingArea()\n",
    "        vbox.add(self.movie_window)\n",
    "        window.show_all()\n",
    "\n",
    "        self.player = Gst.Pipeline.new(\"player\")\n",
    "        source = Gst.ElementFactory.make(\"filesrc\", \"file-source\")\n",
    "        demuxer = Gst.ElementFactory.make(\"qtdemux\", \"demuxer\")\n",
    "        demuxer.connect(\"pad-added\", self.demuxer_callback)\n",
    "        self.video_decoder = Gst.ElementFactory.make(\"avdec_h264\", \"video-decoder\")\n",
    "        self.audio_decoder = Gst.ElementFactory.make(\"faad\", \"audio-decoder\")\n",
    "        audioconv = Gst.ElementFactory.make(\"audioconvert\", \"converter\")\n",
    "        audiosink = Gst.ElementFactory.make(\"autoaudiosink\", \"audio-output\")\n",
    "        videosink = Gst.ElementFactory.make(\"xvimagesink\", \"video-output\")\n",
    "        self.queuea = Gst.ElementFactory.make(\"queue\", \"queuea\")\n",
    "        self.queuev = Gst.ElementFactory.make(\"queue\", \"queuev\")\n",
    "        colorspace = Gst.ElementFactory.make(\"videoconvert\", \"colorspace\")\n",
    "        #colorspace = Gst.ElementFactory.make(\"ffmpegcolorspace\", \"colorspace\")\n",
    "\n",
    "        self.player.add(source)\n",
    "        self.player.add(demuxer)\n",
    "        self.player.add(self.video_decoder)\n",
    "        self.player.add(self.audio_decoder)\n",
    "        self.player.add(audioconv)\n",
    "        self.player.add(audiosink)\n",
    "        self.player.add(videosink)\n",
    "        self.player.add(self.queuea)\n",
    "        self.player.add(self.queuev)\n",
    "        self.player.add(colorspace)\n",
    "        source.link(demuxer)\n",
    "        self.queuev.link(self.video_decoder)\n",
    "        self.video_decoder.link(colorspace)\n",
    "        colorspace.link(videosink)\n",
    "        self.queuea.link(self.audio_decoder)\n",
    "        self.audio_decoder.link(audioconv)\n",
    "        audioconv.link(audiosink)\n",
    "        bus = self.player.get_bus()\n",
    "        bus.add_signal_watch()\n",
    "        bus.enable_sync_message_emission()\n",
    "        bus.connect(\"message\", self.on_message)\n",
    "        bus.connect(\"sync-message::element\", self.on_sync_message)\n",
    "    def start_stop(self, w):\n",
    "        if self.button.get_label() == \"Start\":\n",
    "            filepath = self.entry.get_text().strip()\n",
    "            if os.path.isfile(filepath):\n",
    "                filepath = os.path.realpath(filepath)\n",
    "                self.button.set_label(\"Stop\")\n",
    "                self.player.get_by_name(\"file-source\").set_property(\"location\", filepath)\n",
    "                self.player.set_state(Gst.State.PLAYING)\n",
    "        else:\n",
    "            self.player.set_state(Gst.State.NULL)\n",
    "            self.button.set_label(\"Start\")\n",
    "    def on_message(self, bus, message):\n",
    "        t = message.type\n",
    "        if t == Gst.MessageType.EOS:\n",
    "            self.player.set_state(Gst.State.NULL)\n",
    "            self.button.set_label(\"Start\")\n",
    "        elif t == Gst.MessageType.ERROR:\n",
    "            err, debug = message.parse_error()\n",
    "            print (\"Error: %s\" % err, debug)\n",
    "            self.player.set_state(Gst.State.NULL)\n",
    "            self.button.set_label(\"Start\")\n",
    "    def on_sync_message(self, bus, message):\n",
    "        if message.get_structure().get_name() == 'prepare-window-handle':\n",
    "            imagesink = message.src\n",
    "            imagesink.set_property(\"force-aspect-ratio\", True)\n",
    "            xid = self.movie_window.get_property('window').get_xid()\n",
    "            imagesink.set_window_handle(xid)\n",
    "    def demuxer_callback(self, demuxer, pad):\n",
    "        if pad.get_property(\"template\").name_template == \"video_%u\":\n",
    "            qv_pad = self.queuev.get_static_pad(\"sink\")\n",
    "            pad.link(qv_pad)\n",
    "        elif pad.get_property(\"template\").name_template == \"audio_%u\":\n",
    "            qa_pad = self.queuea.get_static_pad(\"sink\")\n",
    "            pad.link(qa_pad)\n",
    "Gst.init(None)\n",
    "GTK_Main()\n",
    "GObject.threads_init()\n",
    "Gtk.main()\n",
    "```"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python36",
   "language": "python",
   "name": "python36"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
